/*
 * Copyright (c) 2008, Roman Bosak
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the DailyDevelopment nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.dailydev.jira.portal;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.ofbiz.core.entity.GenericEntityException;

import com.atlassian.jira.exception.DataAccessException;
import com.atlassian.jira.ofbiz.OfBizDelegator;
import com.atlassian.jira.portal.PortalManager;
import com.atlassian.jira.portal.PortalPageConfiguration;
import com.atlassian.jira.portal.Portlet;
import com.atlassian.jira.portal.PortletConfigurationException;
import com.atlassian.jira.portal.PortletConfigurationManager;
import com.atlassian.jira.portal.ProfessionalPortalManager;
import com.atlassian.jira.util.CollectionReorderer;
import com.atlassian.plugin.PluginManager;
import com.dailydev.jira.portal.config.IPortalPageSharingConfiguration;
import com.dailydev.jira.portal.config.ISharedPortalPage;
import com.dailydev.jira.portal.config.PortalPageSharingConfigurationException;
import com.opensymphony.user.EntityNotFoundException;
import com.opensymphony.user.User;
import com.opensymphony.user.UserManager;

/**
 * Default implementation of {@link IDasboardSharingPortalManager} using
 * standard Jira {@link ProfessionalPortalManager} as a delegate to provide
 * current functionality and implements functionality related to portal page
 * sharing.
 * 
 * @author Roman Bosak, roman.bosak@gmail.com
 */
public class DasboardSharingPortalManager implements
        IDashboardSharingPortalManager {

    /**
     * Logger for this class.
     */
    private static final Logger LOG = Logger
            .getLogger(DasboardSharingPortalManager.class);

    /**
     * Reference to configuration of portal page sharing.
     */
    private IPortalPageSharingConfiguration configuration;

    /**
     * Reference to user manager.
     */
    private final UserManager userManager;

    /**
     * Reference to standard Jira implementation of {@link PortalManager} to
     * delegate method calls to.
     */
    private PortalManager delegate;

    /**
     * Constructor overrided from {@link ProfessionalPortalManager} class and
     * extended with <code>userManager</code> and <code>configuration</code>.
     * 
     * @param pluginManager
     *            reference to {@link PluginManager} propagated from Jira
     * @param delegator
     *            reference to {@link OfBizDelegator} propagated from Jira
     * @param collectionReorderer
     *            reference to {@link CollectionReorderer} propagated from Jira
     * @param portletConfigurationManager
     *            reference to {@link PortletConfigurationManager} propagated
     *            from Jira
     * @param userManager
     *            reference to {@link UserManager} propagated from Jira
     * @param configuration
     *            reference to {@link IPortalPageSharingConfiguration}
     *            propagated from Jira
     */
    public DasboardSharingPortalManager(final PluginManager pluginManager,
            final OfBizDelegator delegator,
            final CollectionReorderer collectionReorderer,
            final PortletConfigurationManager portletConfigurationManager,
            final UserManager userManager,
            final IPortalPageSharingConfiguration configuration) {
        super();

        this.userManager = userManager;
        this.configuration = configuration;

        delegate = new ProfessionalPortalManager(pluginManager, delegator,
                collectionReorderer, portletConfigurationManager);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Instance created");
        }
    }

    /**
     * This method makes just simple constraint on same method from
     * {@link #delegate}. The constraint is that the delegate method is not
     * called in case when you want to decrease position of page shared to you
     * by other user - it has higher index than last own portal page.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#decreasePageConfigurationSequence(com.opensymphony.user.User,
     *      int)
     */
    public void decreasePageConfigurationSequence(final User remoteUser,
            final int position) throws PortletConfigurationException {
        // get number of own pages
        final int ownPages = getOwnPortalPageConfigurations(remoteUser).size();

        // does it have higher index than last own portal page?
        if (ownPages > position) {
            delegate.decreasePageConfigurationSequence(remoteUser, position);
        }
    }

    /**
     * This method makes just simple constraint on same method from
     * {@link #delegate}. The constraint is that the delegate method is not
     * called in case when you want to delete page which is shared to user by
     * other user. Also sharing configuration of the page is removed if exist.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#deletePortalPageConfiguration(User,
     *      Long)
     */
    public void deletePortalPageConfiguration(final User remoteUser,
            final Long portalPageId) throws PortletConfigurationException {

        PortalPageConfiguration portalPage = delegate
                .getPortalPageConfiguration(remoteUser, portalPageId);
        if (portalPage != null) {
            delegate.deletePortalPageConfiguration(remoteUser, portalPageId);

            try {
                // remove portal page from sharing configuration
                configuration.removeSharedPortalPage(portalPageId);
            } catch (PortalPageSharingConfigurationException e) {
                LOG.warn("Unable to remove portal page sharing configuration ("
                        + portalPageId + ")", e);
            }
        }
    }

    /** {@inheritDoc} */
    public IPortalPageSharingConfiguration getConfiguration() {
        return configuration;
    }

    /** {@inheritDoc} */
    public Map<Long, PortalPageConfiguration> getOwnPortalPageConfigurations(
            final User user) throws PortletConfigurationException {
        Map ownPortalPageConfigurations = delegate
                .getPortalPageConfigurations(user);

        if (LOG.isDebugEnabled()) {
            LOG
                    .debug("User '" + user.getName() + "' has "
                            + ownPortalPageConfigurations.size()
                            + " own portal pages.");
        }

        return ownPortalPageConfigurations;
    }

    /**
     * This method changes behavior of default implementation of this method
     * from {@link ProfessionalPortalManager} in a way that it finds author of
     * the page for pages which are not owned by the <code>user</code> and
     * then calls delegate with found author as an argument.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getPortalPageConfiguration(User,
     *      Long)
     */
    public PortalPageConfiguration getPortalPageConfiguration(User user,
            Long pageId) throws PortletConfigurationException {
        ISharedPortalPage sharedPortalPage = configuration
                .getSharedPortalPage(pageId);

        if (!sharedPortalPage.getAuthor().equals(user.getName())) {
            try {
                return this.getPortalPageConfiguration(sharedPortalPage
                        .getAuthor(), pageId);
            } catch (final EntityNotFoundException e) {
                LOG.warn("Unable to find author of shared dashboard> "
                        + sharedPortalPage.getAuthor(), e);
            }
        }

        return delegate.getPortalPageConfiguration(user, pageId);
    }

    /** {@inheritDoc} */
    public Map getPortalPageConfigurations(final User user)
            throws PortletConfigurationException {
        if (!isUsingDefaultPortalPageConfigurations(user)) {

            // get own portal page configurations
            final Map<Long, PortalPageConfiguration> portalPages = getOwnPortalPageConfigurations(user);
            portalPages.putAll(portalPages);

            portalPages.putAll(getSharedPortalPageConfigurations(user));

            return portalPages;
        }
        return getOwnPortalPageConfigurations(user);
    }

    /** {@inheritDoc} */
    public Map<Long, PortalPageConfiguration> getSharedPortalPageConfigurations(
            final User user) throws PortletConfigurationException {
        final Map<Long, PortalPageConfiguration> sharedPortalPages = new HashMap<Long, PortalPageConfiguration>();
        // iterate through all groups for which there are shared portal
        // pages configured
        for (ISharedPortalPage sharedDashboard : configuration
                .getSharedPortalPages()) {

            // check if user is not author of the shared portal page and
            // that he has view permission for the group
            if (!sharedDashboard.getAuthor().equals(user.getName())
                    && (sharedDashboard.getGroupName() == null || user
                            .inGroup(sharedDashboard.getGroupName()))) {
                try {
                    PortalPageConfiguration portalPageConfiguration = getPortalPageConfiguration(sharedDashboard);
                    sharedPortalPages.put(sharedDashboard.getDashboardId(),
                            portalPageConfiguration);

                } catch (EntityNotFoundException e) {
                    LOG.warn("Unable to find author of shared dashboard> "
                            + sharedDashboard.getAuthor(), e);
                }
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("User '" + user.getName() + "' can view "
                    + sharedPortalPages.size() + " shared portal pages.");
        }
        return sharedPortalPages;
    }

    /**
     * Gets {@link PortalPageConfiguration} for portal page represented by it's
     * sharing configuration
     * 
     * @param sharedDashboard
     * @return
     * @throws PortletConfigurationException
     *             throws for same reason as
     *             {@link PortalManager#getPortalPageConfiguration(User, Long)}
     * @throws EntityNotFoundException
     *             throws for same reason as {@link UserManager#getUser(String)}
     */
    private PortalPageConfiguration getPortalPageConfiguration(
            ISharedPortalPage sharedDashboard)
            throws PortletConfigurationException, EntityNotFoundException {
        return this.getPortalPageConfiguration(sharedDashboard.getAuthor(),
                sharedDashboard.getDashboardId());
    }

    /** {@inheritDoc} */
    public boolean hasAccessToPage(final User user, final Long portalPageId) {
        try {
            ISharedPortalPage sharedDashboard = configuration
                    .getSharedPortalPage(portalPageId);

            if (sharedDashboard != null) {
                if (sharedDashboard.getGroupName() == null
                        || user.inGroup(sharedDashboard.getGroupName())) {
                    return true;
                }
            }
            // catches all Throwable to avoid Jira disturbances by bugs in a
            // plugin
        } catch (Throwable e) {
            LOG
                    .error(
                            "Error while resolving if user has access to shared portal page",
                            e);
        }

        return delegate.hasAccessToPage(user, portalPageId);
    }

    /**
     * This method makes just simple constraint on same method from superclass.
     * The constraint is that the super method is not called in case when you
     * want to increase position of page which is last in the list of own pages
     * or it is a page shared to you by other user.
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#increasePageConfigurationSequence(com.opensymphony.user.User,
     *      int)
     */
    public void increasePageConfigurationSequence(final User remoteUser,
            final int position) throws PortletConfigurationException {
        // get number of own pages
        final int ownPages = getOwnPortalPageConfigurations(remoteUser).size();

        // isn't it the last own page?
        if (ownPages > position) {
            delegate.increasePageConfigurationSequence(remoteUser, position);
        }
    }

    /**
     * Get a portlet configuration for a particular user and page from the
     * database. If it does not exist, the default portlet configuration will be
     * used. If the user will not be found for <code>authorName</code> then
     * {@link EntityNotFoundException} will be thrown.
     * 
     * @param authorName
     *            The name of particular user whos portal page should be get
     * @param dashboardId
     *            id of portal page to get
     * @return portlet configuration for a particular user and page from the
     *         database
     * @throws PortletConfigurationException
     *             throws for same reason as
     *             {@link PortalManager#getPortalPageConfiguration(User, Long)}
     * @throws EntityNotFoundException
     *             throws for same reason as {@link UserManager#getUser(String)}
     */
    protected PortalPageConfiguration getPortalPageConfiguration(
            final String authorName, final Long dashboardId)
            throws PortletConfigurationException, EntityNotFoundException {
        // gets User instance for author's name
        User author = userManager.getUser(authorName);
        return getPortalPageConfiguration(author, dashboardId);
    }

    /**
     * Setter for {@link IPortalPageSharingConfiguration} implementation used by
     * this {@link DasboardSharingPortalManager}
     * 
     * @param configuration
     *            reference to instance of
     *            {@link IPortalPageSharingConfiguration} implementation used by
     *            this instance
     */
    public void setConfiguration(IPortalPageSharingConfiguration configuration) {
        this.configuration = configuration;
    }

    /**
     * Sets {@link #delegate} field to refer to different instance of
     * {@link PortalManager}.
     * 
     * @param delegate
     *            the delegate to set
     */
    public void setDelegate(PortalManager delegate) {
        this.delegate = delegate;
    }

    /* >>>>>>>>>>>>>>> Unchanged delegate methods >>>>>>>>>>>>>>>>>>> */

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#createPortalPageConfiguration(com.opensymphony.user.User,
     *      java.lang.String, java.lang.String, java.lang.Long)
     */
    public Long createPortalPageConfiguration(User u, String pagename,
            String description, Long copyPageId)
            throws PortletConfigurationException {
        return delegate.createPortalPageConfiguration(u, pagename, description,
                copyPageId);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#createPortalPageConfiguration(com.opensymphony.user.User,
     *      java.lang.String, java.lang.String)
     */
    public Long createPortalPageConfiguration(User user, String pagename,
            String description) throws PortletConfigurationException {
        return delegate.createPortalPageConfiguration(user, pagename,
                description);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getAllPortlets()
     */
    public Collection getAllPortlets() {
        return delegate.getAllPortlets();
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getDefaultPortalPage()
     */
    public PortalPageConfiguration getDefaultPortalPage()
            throws PortletConfigurationException {
        return delegate.getDefaultPortalPage();
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getEditablePortalPageConfiguration(com.opensymphony.user.User,
     *      java.lang.Long)
     */
    public PortalPageConfiguration getEditablePortalPageConfiguration(
            User user, Long pageId) throws PortletConfigurationException {
        return delegate.getEditablePortalPageConfiguration(user, pageId);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getPortlet(java.lang.String)
     */
    public Portlet getPortlet(String id) {
        return delegate.getPortlet(id);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#getPortlets(com.opensymphony.user.User)
     */
    public Collection getPortlets(User user) {
        return delegate.getPortlets(user);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#isDefaultPortalPageName(java.lang.String)
     */
    public boolean isDefaultPortalPageName(String pageName) {
        return delegate.isDefaultPortalPageName(pageName);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.ProfessionalPortalManager#isMultiplePortalPagesEnabled()
     */
    public boolean isMultiplePortalPagesEnabled() {
        return delegate.isMultiplePortalPagesEnabled();
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#isUsingDefaultPortalPageConfigurations(com.opensymphony.user.User)
     */
    public boolean isUsingDefaultPortalPageConfigurations(User remoteUser) {
        return delegate.isUsingDefaultPortalPageConfigurations(remoteUser);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#movePortlets(java.lang.Long[],
     *      com.atlassian.jira.portal.PortalPageConfiguration,
     *      com.atlassian.jira.portal.PortalPageConfiguration)
     */
    public void movePortlets(Long[] selectedPortlets,
            PortalPageConfiguration sourcePage,
            PortalPageConfiguration destinationPage)
            throws PortletConfigurationException, GenericEntityException {
        delegate.movePortlets(selectedPortlets, sourcePage, destinationPage);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#moveToEndPageConfigurationSequence(com.opensymphony.user.User,
     *      int)
     */
    public void moveToEndPageConfigurationSequence(User remoteUser, int position)
            throws PortletConfigurationException {
        delegate.moveToEndPageConfigurationSequence(remoteUser, position);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#moveToStartPageConfigurationSequence(com.opensymphony.user.User,
     *      int)
     */
    public void moveToStartPageConfigurationSequence(User remoteUser,
            int position) throws PortletConfigurationException {
        delegate.moveToStartPageConfigurationSequence(remoteUser, position);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field. {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#resetDefault(java.lang.String)
     */
    public void resetDefault(String pagename) {
        delegate.resetDefault(pagename);
    }

    /**
     * Delegates method call to {@link ProfessionalPortalManager} referenced by
     * {@link #delegate} field.
     * 
     * {@inheritDoc}
     * 
     * @see com.atlassian.jira.portal.AbstractPortalManager#updatePortalPage(com.opensymphony.user.User,
     *      java.lang.Long, java.lang.String, java.lang.String)
     */
    public void updatePortalPage(User user, Long pageId, String name,
            String description) throws DataAccessException {
        delegate.updatePortalPage(user, pageId, name, description);
    }

}
